﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
using System.Threading;


namespace Test
{
    public class Dcu
    {
        public event EventHandler<TagReceivedEventArgs> TagReceived;
        public event EventHandler<DcuConnectionChangedEventArgs> ConnectionChanged;

        protected virtual void OnTagReceived(TagReceivedEventArgs e)
        {
            if (TagReceived != null) TagReceived(this, e);
        }
        protected virtual void OnConnectionChanged(DcuConnectionChangedEventArgs e)
        {
            if (ConnectionChanged != null) ConnectionChanged(this, e);
        }

        public readonly IDcuConfig Config;
        private Socket _adapter;
        private readonly TagDataParser _parser;
        private readonly object _pushSync = new object();
        private readonly byte[] _buffer;
        private delegate void PushDelegate();
        private DateTime _lastRx;
        private PushDelegate _push;
        private bool _connected;

        public bool Connected
        {
            get { return _connected; }
            private set
            {
                if (_connected == value) return;
                _connected = value;
                Log(_connected ? "Connected" : "Disconnected");
                OnConnectionChanged(new DcuConnectionChangedEventArgs(_connected));
            }
        }
        public Dcu(IDcuConfig config)
        {
            Config = config;
            _push = Connect;
            _buffer = new byte[Config.BufferSize];
            _parser = new TagDataParser();
            _parser.TagDataReceived += (s, e) => OnTagReceived(new TagReceivedEventArgs(e.Data));
        }
        private void Log(string message)
        {
            Debug.WriteLine(string.Format("[{0:HH:mm:ss.fff}] DCU({1}:{2}) {3}", DateTime.Now, Config.Host, Config.Port, message));
        }
        public void Push()
        {
            lock (_pushSync)
            {
                if (_push != null) _push();
            }
        }
        private void SwitchTo(PushDelegate method)
        {
            lock (_pushSync)
            {
                if (_push == KeepConnecting && method == Connect) return;
                Log("SwitchTo: " + method.Method.Name);
                _push = method;
            }
        }
        private static void KeepConnecting() { }
        private void Connect()
        {
            SwitchTo(KeepConnecting);
            new Thread(ConnectToRemote) { IsBackground = true }.Start();
        }
        private void ConnectToRemote()
        {
            var retry = 0;
            while (true)
            {
                lock (_pushSync)
                {
                    if (_push != KeepConnecting) break;
                }
                try
                {
                    Log(string.Format("Connecting({0})", retry));
                    if (_adapter != null)
                    {
                        if (_adapter.Connected) _adapter.Disconnect(false);
                        _adapter = null;
                    }
                    _adapter = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    _adapter.Connect(Config.Host, Config.Port);
                    Config.SendInitCommand(_adapter);
                    Connected = true;
                    _lastRx = DateTime.Now;
                    SwitchTo(Receive);
                    break;
                }
                catch (Exception ex)
                {
                    Log("Connection failed: " + ex.Message);
                    Thread.Sleep(1000);
                }
                retry++;
            }
        }
        private void Receive()
        {
            try
            {
                if (_adapter.Available < 1)
                {
                    var diff = DateTime.Now.Subtract(_lastRx);
                    if (diff > Config.Timeout)
                    {
                        Log("Timeout!");
                        Connected = false;
                        SwitchTo(Connect);
                        return;
                    }
                    if (diff > Config.HeartbeatInterval)
                    {
                        Log("Heartbeat");
                        Config.SendHeartbeat(_adapter);
                        return;
                    }
                    return;
                }
                var len = _adapter.Receive(_buffer, 0, _buffer.Length, SocketFlags.None);
                if (len < 1) return;
                _lastRx = DateTime.Now;
                _parser.Parse(Encoding.ASCII.GetString(_buffer, 0, len));
            }
            catch (Exception ex)
            {
                Log("Receive Error: " + ex.Message);
                Connected = false;
                SwitchTo(Connect);
            }
        }
        private void CloseConnection()
        {
            try
            {
                if (_adapter.Connected) Config.SendCloseCommand(_adapter);
                _adapter.Disconnect(false);
            }
            catch (Exception ex)
            {
                Log("Disconnection Error: "+ ex.Message);
            }
            finally
            {
                _adapter = null;
                Connected = false;
                SwitchTo(Connect);
            }
        }
        public void Disconnect()
        {
            Log("Disconnecting");
            SwitchTo(CloseConnection);
        }
    }
    public class DcuConnectionChangedEventArgs : EventArgs
    {
        public bool Connected { get; private set; }
        public DcuConnectionChangedEventArgs(bool connected)
        {
            Connected = connected;
        }
    }
    public interface IDcuConfig
    {
        string Host { get; set; }
        int Port { get; set; }
        TimeSpan Timeout { get; set; }
        TimeSpan HeartbeatInterval { get; set; }
        int BufferSize { get; set; }
        string ReaderId { get; set; }
        string Range { get; set; }
        void SendInitCommand(Socket adapter);
        void SendHeartbeat(Socket adapter);
        void SendCloseCommand(Socket adapter);
        IDcuConfig Clone();
    }
    public class DcuConfig : IDcuConfig
    {
        public string Host { get; set; }
        public int Port { get; set; }
        public TimeSpan Timeout { get; set; }
        public TimeSpan HeartbeatInterval { get; set; }
        public int BufferSize { get; set; }
        public string ReaderId { get; set; }
        public string Range { get; set; }
        #region Commands
        public string Send(string command, Socket adapter)
        {
            var commandStr = ":" + command + "\r\n";
            var packet = Encoding.ASCII.GetBytes(commandStr);
            adapter.Send(packet);
            Thread.Sleep(0);
            var list = new List<byte>();
            var buff = new byte[1];
            var error = 0;
            while (error < 10)
            {
                var len = adapter.Receive(buff, 0, buff.Length, SocketFlags.None);
                if (len < 1)
                {
                    error++;
                    continue;
                }
                if (buff[0] == '\r') continue;
                if (buff[0] == '\n') break;
                list.Add(buff[0]);
            }
            return Encoding.ASCII.GetString(list.ToArray());
        }
        public void SendOnly(string command, Socket adapter)
        {
            var commandStr = ":" + command + "\r\n";
            var packet = Encoding.ASCII.GetBytes(commandStr);
            adapter.Send(packet);
        }
        public void SendHeartbeatCommand(Socket adapter) { SendOnly("START", adapter); }
        public string SendStart(Socket adapter) { return Send("START", adapter); }
        public string SendStop(Socket adapter) { return Send("STOP", adapter); }
        //public void SendReset() { Send("RST MCU"); }
        //public void ResetTcp() { Send("RST TCP"); }
        //public void ReadRssi() { Send("RSSI:START"); }
        //public void StopRssi() { Send("RSSI:STOP"); }
        public string GetStatus(Socket adapter) { return Send("CK?", adapter); }
        //public void GetVersion() { Send("FW?"); }

        /// <summary>Set reader ID</summary>
        /// <param name="id">0000000-999999</param>
        public string SetId(string id, Socket adapter) { return Send("ID " + id, adapter); }
        public string GetId(Socket adapter) { return Send("ID?", adapter); }

        public string SetRange(string range, Socket adapter) { return Send("RSSI:RANG " + range, adapter); }
        public string GetRange(Socket adapter) { return Send("RSSI:RANG?", adapter); }
        #endregion Commands
        private static void Log(Socket adapter, string message)
        {
            Debug.WriteLine(string.Format("[{0:HH:mm:ss.fff}] SendCommand({1}) {2}", DateTime.Now, adapter.RemoteEndPoint, message));
        }

        public virtual void SendInitCommand(Socket adapter)
        {
            Log(adapter, "SendStop " + SendStop(adapter));
            Log(adapter, "SetID " + SetId(ReaderId, adapter));
            Log(adapter, "SetRange " + SetRange(Range, adapter));
            Log(adapter, "SendStart " + SendStart(adapter));
        }
        public virtual void SendHeartbeat(Socket adapter)
        {
            SendHeartbeatCommand(adapter);
        }
        public virtual void SendCloseCommand(Socket adapter)
        {
            Log(adapter, "SendStop " + SendStop(adapter));
        }
        public IDcuConfig Clone()
        {
            var config =
                new DcuConfig
                {
                    BufferSize = BufferSize,
                    HeartbeatInterval = HeartbeatInterval,
                    Timeout = Timeout,
                    Host = Host,
                    Port = Port,
                    Range = Range,
                    ReaderId = ReaderId
                };
            return config;
        }
    }
    public class TagReceivedEventArgs : EventArgs
    {
        public TagData Tag { get; private set; }
        public TagReceivedEventArgs(TagData tag)
        {
            Tag = tag;
        }
    }
    public class TagDataParser
    {
        public event EventHandler<DataEventArgs<KeyValuePair<string, string>>> StatusReceived;
        public event EventHandler<DataEventArgs<TagData>> TagDataReceived;

        protected virtual void OnStatusReceived(DataEventArgs<KeyValuePair<string, string>> e)
        {
            if (StatusReceived != null) StatusReceived(this, e);
        }
        protected virtual void OnTagDataReceived(DataEventArgs<TagData> e)
        {
            if (TagDataReceived != null) TagDataReceived(this, e);
        }

        private Action<char> _h;
        private readonly List<char> _list;
        private string _head;
        private string _data;
        public TagDataParser()
        {
            _h = ReadPrefix;
            _list = new List<char>();
        }
        public void Parse(string s)
        {
            foreach (var c in s) _h(c);
        }
        private void ReadPrefix(char c)
        {
            switch (c)
            {
                case '$': _h = ReadTag; break;
                case ':': _h = ReadHead; break;
                default: return;
            }
            _list.Clear();
            _head = null;
            _data = null;
        }
        private void ReadHead(char c)
        {
            switch (c)
            {
                case ' ':
                    _h = ReadData;
                    _head = new string(_list.ToArray());
                    _list.Clear();
                    return;
                case '\r':
                    _h = ReadSuffix;
                    _head = new string(_list.ToArray());
                    _list.Clear();
                    return;
                default:
                    _list.Add(c);
                    break;
            }
        }
        private void ReadData(char c)
        {
            if (c == '\r')
            {
                _data = new string(_list.ToArray());
                _list.Clear();
                _h = ReadSuffix;
                return;
            }
            _list.Add(c);
        }
        private void ReadTag(char c)
        {
            if (c == '\r')
            {
                _data = new string(_list.ToArray());
                _list.Clear();
                _h = ReadSuffix;
                return;
            }
            _list.Add(c);
        }
        private void ReadSuffix(char c)
        {
            _h = ReadPrefix;
            if (string.IsNullOrEmpty(_head))
            {
                try
                {
                    var tag = new TagData(_data);
                    OnTagDataReceived(new DataEventArgs<TagData>(tag));
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                return;
            }
            OnStatusReceived(new DataEventArgs<KeyValuePair<string, string>>(new KeyValuePair<string, string>(_head, _data)));
        }
    }
    public class DataEventArgs<T> : EventArgs
    {
        public T Data { get; private set; }
        public DataEventArgs(T data)
        {
            Data = data;
        }
    }
    public enum ReaderStatus
    {
        Connecting,
        Connected,
        Disconnecting,
        Disconnected,
        Error
    }
    public class ReaderStatusChangedEventArgs : EventArgs
    {
        public string ReaderId { get; private set; }
        public ReaderStatus Status { get; private set; }
        public ReaderStatusChangedEventArgs(string readerId, ReaderStatus status)
        {
            ReaderId = readerId;
            Status = status;
        }
    }
    public class DcuController
    {
        public event EventHandler<ReaderStatusChangedEventArgs> ReaderStatusChanged;
        protected virtual void OnReaderStatusChanged(ReaderStatusChangedEventArgs e)
        {
            if (ReaderStatusChanged != null) ReaderStatusChanged(this, e);
        }
        public event EventHandler<DataEventArgs<TagData>> TagReceived;
        protected virtual void OnTagReceived(object sender, DataEventArgs<TagData> e)
        {
            if (TagReceived != null) TagReceived(sender, e);
        }

        private readonly List<Dcu> _list;
        private readonly ManualResetEvent _keepRunning;
        public bool IsRunning { get { return !_keepRunning.WaitOne(0, false); } }
        public string[] Readers
        {
            get
            {
                var list = new List<string>();
                foreach (var i in _list.ToArray())
                    list.Add(i.Config.ReaderId);
                return list.ToArray();
            }
        }
        public DcuController()
        {
            _list = new List<Dcu>();
            _keepRunning = new ManualResetEvent(false);
        }
        public void Add(string host, int port, string id, byte range)
        {
            var config =
                new DcuConfig
                {
                    BufferSize = 0xFFFF,
                    HeartbeatInterval = TimeSpan.FromSeconds(5),
                    Timeout = TimeSpan.FromSeconds(10),
                    Host = host,
                    Port = port,
                    Range = range.ToString(),
                    ReaderId = id
                };
            var d = new Dcu(config);
            d.ConnectionChanged +=
                (s, e) =>
                    OnReaderStatusChanged(
                    new ReaderStatusChangedEventArgs(id, e.Connected ? ReaderStatus.Connected : ReaderStatus.Error));
            d.TagReceived += (s, e) => OnTagReceived(s, new DataEventArgs<TagData>(e.Tag));
            _list.Add(d);
        }
        public void Start()
        {
            _keepRunning.Reset();
            new Thread(Push) { IsBackground = true }.Start();
        }
        private void Push()
        {
            while (!_keepRunning.WaitOne(0, false))
            {
                foreach (var i in _list.ToArray())
                {
                    if (i != null)
                    {
                        i.Push();
                    }
                }
            }
            foreach (var i in _list.ToArray())
            {
                if (i != null)
                {
                    i.Disconnect();
                }
            }
        }
        public void Stop()
        {
            _keepRunning.Set();
        }
        public void Reset()
        {
            Stop();
            Start();
        }
    }

    public class TagLocationController
    {
        public event EventHandler LocationChanged;
        protected virtual void OnLocationChanged(EventArgs e)
        {
            if (LocationChanged != null) LocationChanged(this, e);
        }

        private readonly Dictionary<string, string> _tagAreas;
        private readonly Dictionary<string, List<TagData>> _areaTags;
        private readonly Dictionary<string, string> _output;
        private readonly Dictionary<string, string> _tagNames;

        public Dictionary<string, string> AreaInfo { get { return _output; } }

        public TagLocationController(Dictionary<string , string> tagNames)
        {
            _tagNames = tagNames;
            _tagAreas = new Dictionary<string, string>();
            _areaTags = new Dictionary<string, List<TagData>>();
            _output = new Dictionary<string, string>();
        }
        
        public int Update(params TagData[] tags)
        {
            var changed = 0;
            foreach (var tag in tags)
            {
                if (_tagAreas.ContainsKey(tag.TagId))
                {
                    if (_tagAreas[tag.TagId] == tag.ReaderId) continue;
                    var area = _tagAreas[tag.TagId];
                    foreach (var i in _areaTags[area].ToArray())
                    {
                        if (i.TagId != tag.TagId) continue;
                        _areaTags[area].Remove(i);
                        break;
                    }
                }
                _tagAreas[tag.TagId] = tag.ReaderId;
                if (!_areaTags.ContainsKey(tag.ReaderId))
                    _areaTags[tag.ReaderId] = new List<TagData>();
                _areaTags[tag.ReaderId].Add(tag);
                changed++;
            }
            if (changed < 1) return 0;
            _output.Clear();
            foreach (var i in _areaTags)
            {
                var sb = new StringBuilder();
                foreach (var j in i.Value)
                {
                    var s =
                        (_tagNames.ContainsKey(j.TagId) ? _tagNames[j.TagId] : j.TagId) + " " +
                        (j.KeyPressed ? "呼" : "__") +
                        (j.TakingOff ? "脫" : "__") +
                        (j.IdleTime > TimeSpan.FromSeconds(10) ? "停" : "__");
                    sb.AppendLine(s);
                }
                _output[i.Key] = sb.ToString();
            }
            return changed;
        }
    }
}
